package org.balthie.cal.common.spec;

import org.balthie.cal.common.ex.InputException;

/**
 * Abstract base implementation of composite
 * {@link org.springframework.data.jpa.domain.Specification} with default
 * implementations for {@code and}, {@code or} and {@code not}.
 */
public abstract class AbstractSpecification<T> implements ISpecification<T> {

	/**
	 * {@inheritDoc}
	 */
	public ISpecification<T> and(final ISpecification<T> specification) {
		return new AndSpecification<T>(this, specification);
	}

	/**
	 * {@inheritDoc}
	 */
	public ISpecification<T> or(final ISpecification<T> specification) {
		return new OrSpecification<T>(this, specification);
	}

	/**
	 * {@inheritDoc}
	 */
	public ISpecification<T> not(final ISpecification<T> specification) {
		return new NotSpecification<T>(this, specification);
	}

	/**
	 * AND specification, used to create a new specifcation that is the AND of two
	 * other specifications.
	 */
	class AndSpecification<T> extends AbstractSpecification<T> {

		private AbstractSpecification<T> spec1;

		private AbstractSpecification<T> spec2;

		/**
		 * Create a new AND specification based on two other spec.
		 *
		 * @param spec1 Specification one.
		 * @param spec2 Specification two.
		 */
		public AndSpecification(final ISpecification<T> spec1, final ISpecification<T> spec2) {
			this.spec1 = (AbstractSpecification<T>) spec1;
			this.spec2 = (AbstractSpecification<T>) spec2;
		}

		/**
		 * {@inheritDoc}
		 */
		public boolean isSatisfiedBy(final T t) {
			return spec1.isSatisfiedBy(t) && spec2.isSatisfiedBy(t);
		}

		@Override
		public void check(T candidate) {
			if (!spec1.isSatisfiedBy(candidate))
				spec1.check(candidate);

			if (!spec2.isSatisfiedBy(candidate))
				spec2.check(candidate);
		}
	}

	/**
	 * NOT decorator, used to create a new specifcation that is the inverse (NOT) of
	 * the given spec.
	 */
	class NotSpecification<T> extends AbstractSpecification<T> {

		private ISpecification<T> spec1;
		private ISpecification<T> spec2;

		/**
		 * Create a new NOT specification based on another spec.
		 * 
		 * @param spec1 Specification instance before not
		 * @param spec2 Specification instance to not.
		 */
		public NotSpecification(final ISpecification<T> spec1, ISpecification<T> spec2) {
			this.spec1 = spec1;
			this.spec2 = spec2;
		}

		/**
		 * {@inheritDoc}
		 */
		public boolean isSatisfiedBy(final T t) {
			return !spec2.isSatisfiedBy(t) && spec1.isSatisfiedBy(t);
		}

		@Override
		public void check(T t) {
			// 无论 spec1 成不成立，spec2不能成立
			boolean notSpec2 = Boolean.TRUE;
			try {
				spec2.check(t);
				// not spec2 不成立
				notSpec2 = Boolean.FALSE;
			} catch (Exception e) {
			}

			if (!notSpec2) {
				// not spec2 不成立 , 抛出异常
				throw new InputException(spec2.getTips());
			} else {
				// not spec2 成立 再检查 spec1
				spec1.check(t);
			}
		}
	}

	/**
	 * OR specification, used to create a new specifcation that is the OR of two
	 * other specifications.
	 */
	class OrSpecification<T> extends AbstractSpecification<T> {

		private AbstractSpecification<T> spec1;

		private AbstractSpecification<T> spec2;

		/**
		 * Create a new OR specification based on two other spec.
		 *
		 * @param spec1 Specification one.
		 * @param spec2 Specification two.
		 */
		public OrSpecification(final ISpecification<T> spec1, final ISpecification<T> spec2) {
			this.spec1 = (AbstractSpecification<T>) spec1;
			this.spec2 = (AbstractSpecification<T>) spec2;
		}

		/**
		 * {@inheritDoc}
		 */
		public boolean isSatisfiedBy(final T t) {
			return spec1.isSatisfiedBy(t) || spec2.isSatisfiedBy(t);
		}

		@Override
		public void check(T candidate) {
			if (this.isSatisfiedBy(candidate)) {
				return;
			} else {
				spec2.check(candidate);
				spec1.check(candidate);
			}
		}
	}
}
